#include "player.h"
#include "map.h"
#include "io.h"
#include "item.h"

const uint Player::INVENTORY_WIDTH  = 48;
const uint Player::INVENTORY_HEIGHT = 17;
const uint Player::STATS_HEIGHT     = 4;
std::vector< Player::HelpEntry > Player::_help;

Player::Player( int sStr, int sDex, int sInt, int randomlyDistributedPoints )
: Creature( sStr, sDex, sInt, randomlyDistributedPoints )
, _inventoryPage( 0 )
, _helpPage( 0 )
{
    _hp = 50;
    _mp = 50;

    if ( !_help.empty() )
        return;
    _help.push_back( HelpEntry(   0, "Information" ) );
    _help.push_back( HelpEntry( '?', "help" ) );
    _help.push_back( HelpEntry( ';', "look" ) );
    _help.push_back( HelpEntry( 'i', "inventory" ) );
    _help.push_back( HelpEntry( 'n', "name (NYI)" ) );
    _help.push_back( HelpEntry( 'N', "notes" ) );
    _help.push_back( HelpEntry( 'H', "history" ) );
    _help.push_back( HelpEntry(   0, "Actions" ) );
    _help.push_back( HelpEntry( '5', "wait" ) );
    _help.push_back( HelpEntry( '>', "go down" ) );
    _help.push_back( HelpEntry( '<', "go up" ) );
    _help.push_back( HelpEntry( '.', "pick up" ) );
    _help.push_back( HelpEntry( ',', "pick up (and wield)" ) );
    _help.push_back( HelpEntry( 'd', "drop" ) );
    _help.push_back( HelpEntry( 'e', "eat or drink" ) );
    _help.push_back( HelpEntry( 'f', "fight (NYI)" ) );
    _help.push_back( HelpEntry( 'k', "kick (NYI)" ) );
    _help.push_back( HelpEntry( 'l', "set alight (inventory)" ) );
    _help.push_back( HelpEntry( 'L', "set alight (nearby)" ) );
    _help.push_back( HelpEntry( 'p', "push" ) );
    _help.push_back( HelpEntry( 'P', "pull" ) );
    _help.push_back( HelpEntry( 'r', "remove (worn or wielded)" ) );
    _help.push_back( HelpEntry( 't', "throw (NYI)" ) );
    _help.push_back( HelpEntry( 'T', "talk (NYI)" ) );
    _help.push_back( HelpEntry( 'Q', "quit" ) );
    _help.push_back( HelpEntry( 'w', "wear" ) );
    _help.push_back( HelpEntry( 'W', "wield" ) );
    _help.push_back( HelpEntry(   0, "Movement" ) );
    _help.push_back( HelpEntry( Sym::UPARROW, "up" ) );
    _help.push_back( HelpEntry( Sym::DOWNARROW, "down" ) );
    _help.push_back( HelpEntry( Sym::LEFTARROW, "left" ) );
    _help.push_back( HelpEntry( Sym::RIGHTARROW, "right" ) );
    _help.push_back( HelpEntry( '1', "down-left" ) );
    _help.push_back( HelpEntry( '2', "down" ) );
    _help.push_back( HelpEntry( '3', "down-right" ) );
    _help.push_back( HelpEntry( '4', "left" ) );
    _help.push_back( HelpEntry( '6', "right" ) );
    _help.push_back( HelpEntry( '7', "up-left" ) );
    _help.push_back( HelpEntry( '8', "up" ) );
    _help.push_back( HelpEntry( '9', "up-right" ) );

}

Player::~Player()
{
}

bool Player::Priority( int* priority ) const
{
    *priority += 10000;
    return true;
}

bool Player::Symbol( IO::SymList* list ) const
{
    list->push_back( IO::Sym( '@', Colour( 255, 255, 255 ) ) );
    return true;
}

bool Player::OnAction( int* speedMod )
{
    RenderMap();
    RenderStats();
    DisplayMessages();

    bool b = false;
    while ( !b ) {
        uint in = GetMap()->GetAsyncIO().Input();
        ConstItemList list;
        GetItemsInInventory( list );

        // Far look
        if ( in == ';' ) {
            Bank().PlayerUI( PLAYERUI_QUERY_LOOK, this );
            DisplayMessages();

            uint x, y;
            if ( InputTile( x, y, ';' ) )
                FarLook( x, y );
            else
                DisplayMessages();
        }

        // Notes
        if ( in == 'N' )
            GetMap()->GetGame().DisplayNotes();

        // History
        if ( in == 'H' )
            GetMap()->GetGame().DisplayHistory();

        // Movement
        uint d = GetDirection( in );
        if ( d != 0 && d != 5 )
            b = TryMove( d, speedMod );
        if ( d == 5 )
            b = true;

        // Portals
        if ( in == INPUT_CONFIRM || in == '>' || in == '<' ) {
            ActorList list;
            GetMap()->GetTile( GetX(), GetY() ).GetActorsOnTile< BIPortal >( list );
            foreach ( Actor* a, list ) {
                Map* target;
                uint tx, ty, type;
                a->Call< BIPortal >( &target, &tx, &ty, &type );
                if ( in == INPUT_CONFIRM || type == BIPortal::PORTAL_ANY || ( in == '>' && type == BIPortal::PORTAL_DOWN ) || ( in == '<' && type == BIPortal::PORTAL_UP ) ) {
                    SetMap( target, tx, ty );
                    b = true;
                    break;
                }
            }
        }

        // Push
        if ( in == 'p' || in == 'P' ) {
            Bank().PlayerUI( in == 'p' ? PLAYERUI_QUERY_PUSH : PLAYERUI_QUERY_PULL, this );
            DisplayMessages();
            uint d;
            if ( InputDirection( d ) )
                b = in == 'p' ? TryPush( d, speedMod ) : TryPull( d, speedMod );
            else
                DisplayMessages();
        }

        // Pick up
        if ( in == '.' || in == ',' ) {
            ConstActorList actors;
            GetMap()->GetTile( GetX(), GetY() ).GetActorsOnTile< BIIsItem >( actors );
            if ( actors.empty() ) {
                if ( !CanSeeTile( GetX(), GetY() ) ) {
                    Bank().PlayerUI( PLAYERUI_PICKUP_DARK, this );
                    b = true;
                }
                Bank().PlayerUI( PLAYERUI_NOTHING_HERE, this );
                DisplayMessages();
            }
            else {
                uint count = InputItem( in == '.' ? PICKUP : PICKUP_WIELD );
                b = count > 0;
                *speedMod += count * 2;
            }
            GetMap()->GetTile( GetX(), GetY() ).Memorise( *GetMap(), GetX(), GetY(), true );
        }

        // Drop inventory, wear, wield, remove, eat
        if ( in == 'd' || in == 'i' || in == 'w' || in == 'W' || in == 'r' || in == 'e' ) {
            if ( list.empty() ) {
                Bank().PlayerUI( PLAYERUI_NOTHING_INVENTORY, this );
                DisplayMessages();
            }
            else {
                uint count = InputItem( in == 'd' ? DROP : in == 'i' ? LOOK : in == 'w' ? WEAR : in == 'W' ? WIELD : in == 'r' ? REMOVE : EAT );
                b = count > 0;
                *speedMod += count * 2;
                if ( b )
                    GetMap()->GetTile( GetX(), GetY() ).Memorise( *GetMap(), GetX(), GetY(), true );
            }
        }

        // Set alight
        if ( in == 'l' || in == 'L' ) {
            bool canDo = false;
            ConstItemList flist;
            GetItemsInInventory( flist, 0, BIFlammable::FIRE_SOURCE );
            ConstActorList alist;
            GetAdjacentActors( alist, BIFlammable::FIRE_SOURCE );
            if ( flist.empty() && alist.empty() ) {
                Bank().PlayerUI( PLAYERUI_NOTHING_FIRESOURCE, this );
                DisplayMessages();
            }
            else if ( in == 'l' && list.empty() ) {
                Bank().PlayerUI( PLAYERUI_NOTHING_INVENTORY, this );
                DisplayMessages();
            }
            else {
                uint d = 0;
                if ( in == 'l' ) {
                    uint count = InputItem( LIGHT );
                    b = count > 0;
                    *speedMod += count * 2;
                }
                else {
                    Bank().PlayerUI( PLAYERUI_QUERY_LIGHT, this );
                    DisplayMessages();
                    if ( InputDirection( d ) ) {
                        ActorList targets;
                        GetMap()->GetTile( GetX( d ), GetY( d ) ).GetActorsOnTile( targets );
                        Behaved* source;
                        if ( ( source = GetBestFireSource() ) != 0 ) {
                            for ( uint i = 0; i < targets.size(); ++i ) {
                                Actor* a = targets[ i ];
                                uint fire;
                                a->Call< BIFire >( &fire );
                                if ( ( fire & BIFire::FIRE_ON_FIRE || fire & BIFire::FIRE_SMOULDERING ) && i < targets.size() - 1 )
                                    continue;
                                source->Call< BIOnLight >( this, a );
                                b = true;
                                break;
                            }
                        }
                    }
                }
            }
        }

        // Help
        if ( in == '?' ) {
            RenderHelp();
        }

        if ( in == 'Q' )
            throw QuitException();
    }
    return true;
}

bool Player::IIMPrioritySort( const ItemInputMapEntry& less, const ItemInputMapEntry& greater )
{
    return Item::PrioritySort( less.second[ 0 ], greater.second[ 0 ] );
}

bool Player::IIMCategorySort( const ItemInputMapEntry& less, const ItemInputMapEntry& greater )
{
    return Item::CategorySort( less.second[ 0 ], greater.second[ 0 ] );
}

bool Player::AIMPrioritySort( const ActorInputMapEntry& less, const ActorInputMapEntry& greater )
{
    return Actor::PrioritySort( less.second[ 0 ], greater.second[ 0 ] );
}

void Player::GetItemInputMap( const ActorList& actors, ItemInputMap& output, bool sortCategories )
{
    ItemList items;
    foreach ( Actor* a, actors ) {
        Item* i;
        if ( a->Call< BIIsItem >( &i ) ) {
            items.push_back( i );
        }
    }
    GetItemInputMap( items, output, sortCategories );
}

void Player::GetItemInputMap( const ItemList& items, ItemInputMap& output, bool sortCategories )
{
    ItemList tempList;
    foreach ( Item* i, items ) {
        if ( !i->IsDestroyed() )
            tempList.push_back( i );
    }

    bool ignore;

    // Remove items after first whose names have changed
    for ( uint i = 0; i < output.size(); i++ ) {
        std::string s = "";
        if ( output[ i ].second.size() > 0 )
            output[ i ].second[ 0 ]->Call< BIName >( &s, &ignore, ARTICLE_INDEFINITE );
        for ( uint j = 0; j < output[ i ].second.size(); j++ ) {
            std::string t;
            output[ i ].second[ j ]->Call< BIName >( &t, &ignore, ARTICLE_INDEFINITE );
            if ( j > 0 && ( t.compare( s ) != 0 || output[ i ].second[ 0 ]->IsWielded() != output[ i ].second[ j ]->IsWielded() ||
                                                   output[ i ].second[ 0 ]->IsWorn()    != output[ i ].second[ j ]->IsWorn() ) ) {
                output[ i ].second.erase( output[ i ].second.begin() + j );
                j--;
                continue;
            }
        }
        if ( !output[ i ].second.size() ) {
            output.erase( output.begin() + i );
            i--;
        }
    }
    // Remove items whose names are duplicates
    std::vector< std::string > names;
    std::vector< bool > worn;
    std::vector< bool > wielded;
    for ( uint i = 0; i < output.size(); i++ ) {
        std::string s = "";
        if ( output[ i ].second.size() > 0 )
            output[ i ].second[ 0 ]->Call< BIName >( &s, &ignore, ARTICLE_INDEFINITE );
        for ( uint j = 0; j < names.size(); j++ ) {
            if ( names[ j ].compare( s ) == 0 && output[ i ].second[ 0 ]->IsWielded() == wielded[ j ] &&
                                                 output[ i ].second[ 0 ]->IsWorn()    ==    worn[ j ] ) {
                output.erase( output.begin() + i );
                i--;
                break;
            }
        }
        if ( !output[ i ].second.size() ) {
            output.erase( output.begin() + i );
            i--;
        }
        names.push_back( s );
        worn.push_back( output[ i ].second[ 0 ]->IsWorn() );
        wielded.push_back( output[ i ].second[ 0 ]->IsWielded() );
    }
    // Remove items that aren't here any more
    for ( uint i = 0; i < output.size(); i++ ) {
        for ( uint j = 0; j < output[ i ].second.size(); j++ ) {
            bool b = false;
            for ( uint k = 0; k < tempList.size(); k++ ) {
                if ( tempList[ k ] == output[ i ].second[ j ] ) {
                    b = true;
                    tempList.erase( tempList.begin() + k );
                    break;
                }
            }
            if ( !b ) {
                output[ i ].second.erase( output[ i ].second.begin() + j );
                j--;
            }
        }
        if ( !output[ i ].second.size() ) {
            output.erase( output.begin() + i );
            i--;
        }
    }

    std::stable_sort( tempList.begin(), tempList.end(), &Item::PrioritySort );
    if ( sortCategories )
        std::stable_sort( tempList.begin(), tempList.end(), &Item::CategorySort );

    // Add new ones
    for ( uint i = 0; i < tempList.size(); i++ ) {
        uint pos = -1;
        for ( uint j = 0; j < output.size(); j++ ) {
            std::string s, t;
            tempList[ i ]->Call< BIName >( &s, &ignore, ARTICLE_INDEFINITE );
            output[ j ].second[ 0 ]->Call< BIName >( &t, &ignore, ARTICLE_INDEFINITE );
            if ( s.compare( t ) == 0 && tempList[ i ]->IsWielded() == output[ j ].second[ 0 ]->IsWielded() &&
                                        tempList[ i ]->IsWorn()    == output[ j ].second[ 0 ]->IsWorn() ) {
                pos = j;
            }
        }
        if ( pos == -1 ) {
            uint c = 0;
            while ( true ) {
                bool b = false;
                foreach ( const ItemInputMapEntry& p, output ) {
                    if ( p.first == c )
                        b = true;
                }
                if ( !b )
                    break;
                c++;
            }
            output.push_back( ItemInputMapEntry( c, ItemList() ) );
            pos = output.size() - 1;
        }
        output[ pos ].second.push_back( tempList[ i ] );
    }
    std::stable_sort( output.begin(), output.end(), &IIMPrioritySort );
    if ( sortCategories )
        std::stable_sort( output.begin(), output.end(), &IIMCategorySort );
}

void Player::GetActorInputMap( const ActorList& actors, ActorInputMap& output )
{
    ActorList tempList;
    foreach ( Actor* a, actors ) {
        if ( !a->IsDestroyed() )
            tempList.push_back( a );
    }

    bool ignore;

    // Remove actors that aren't here any more
    for ( uint i = 0; i < output.size(); i++ ) {
        for ( uint j = 0; j < output[ i ].second.size(); j++ ) {
            bool b = false;
            for ( uint k = 0; k < tempList.size(); k++ ) {
                if ( tempList[ k ] == output[ i ].second[ j ] ) {
                    b = true;
                    tempList.erase( tempList.begin() + k );
                    break;
                }
            }
            if ( !b ) {
                output[ i ].second.erase( output[ i ].second.begin() + j );
                j--;
            }
        }
        if ( !output[ i ].second.size() ) {
            output.erase( output.begin() + i );
            i--;
        }
    }
    
    std::stable_sort( tempList.begin(), tempList.end(), &Actor::PrioritySort );

    // Add new ones
    for ( uint i = 0; i < tempList.size(); i++ ) {
        uint pos = -1;
        for ( uint j = 0; j < output.size(); j++ ) {
            std::string tn, on;
            tempList[ i ]->Call< BIName >( &tn, &ignore, ARTICLE_INDEFINITE );
            output[ j ].second[ 0 ]->Call< BIName >( &on, &ignore, ARTICLE_INDEFINITE );
            if ( tn.compare( on ) == 0 )
                pos = j;
        }
        if ( pos == -1 ) {
            uint c = 0;
            while ( true ) {
                bool b = false;
                foreach ( const ActorInputMapEntry& p, output ) {
                    if ( p.first == c )
                        b = true;
                }
                if ( !b )
                    break;
                c++;
            }
            output.push_back( ActorInputMapEntry( c, ActorList() ) );
            pos = output.size() - 1;
        }
        output[ pos ].second.push_back( tempList[ i ] );
    }
    std::stable_sort( output.begin(), output.end(), &AIMPrioritySort );
}

std::string Player::GetItemInputName( const ItemList& mapEntry )
{
    if ( !CanSeeTile( GetX(), GetY() ) && mapEntry[ 0 ]->GetActor() != this )
        return "unknown";
    std::string r;
    bool ignore;
    if ( mapEntry.size() > 1 ) {
        std::stringstream ss;
        ss << mapEntry.size();
        std::string name;
        mapEntry[ 0 ]->Call< BIName >( &name, &ignore, ARTICLE_PLURAL );
        r += ss.str() + " " + name;
    }
    else {
        std::string name;
        mapEntry[ 0 ]->Call< BIName >( &name, &ignore, ARTICLE_INDEFINITE );
        r += name;
    }
    return r;
}

std::string Player::GetActorInputName( const ActorList& mapEntry )
{
    if ( !CanSeeActor( mapEntry[ 0 ] ) && mapEntry[ 0 ] != this )
        return "unknown";
    std::string r;
    std::string n;
    bool ignore;
    if ( mapEntry.size() > 1 ) {
        std::stringstream ss;
        ss << mapEntry.size();
        mapEntry[ 0 ]->Call< BIName >( &n, &ignore, ARTICLE_PLURAL );
        r += ss.str() + " " + n;
    }
    else {
        mapEntry[ 0 ]->Call< BIName >( &n, &ignore, ARTICLE_INDEFINITE );
        r += n;
    }
    return r;
}

bool Player::InputTile( uint& x, uint& y, uint inputConfirm ) const
{
    x = GetX();
    y = GetY();

    while ( true ) {
        int vx = x, vy = y;
        if ( MapToViewCoordinates( vx, vy ) )
            GetMap()->GetSyncIO()._io.ShowCursor( vx, vy );
        else
            GetMap()->GetSyncIO()._io.HideCursor();
        GetMap()->GetSyncIO().Render();

        uint in = GetMap()->GetAsyncIO().Input();
        if ( in == INPUT_CONFIRM || in == inputConfirm ) {
            GetMap()->GetSyncIO()._io.HideCursor();
            return true;
        }
        if ( in == INPUT_CANCEL ) {
            GetMap()->GetSyncIO()._io.HideCursor();
            return false;
        }

        uint d = GetDirection( in );
        if ( ( d == 1 || d == 4 || d == 7 ) && x > 0 && vx > 0 )
            x--;
        if ( ( d == 3 || d == 6 || d == 9 ) && x < GetMap()->GetWidth() - 1 && vx < int( Game::WIDTH ) - 1 )
            x++;
        if ( ( d == 1 || d == 2 || d == 3 ) && y < GetMap()->GetHeight() - 1 && vy < int( Game::HEIGHT ) - 1 )
            y++;
        if ( ( d == 7 || d == 8 || d == 9 ) && y > 0 && vy > 0 )
            y--;
    }
}

bool Player::InputDirection( uint& d ) const
{
    while ( true ) {
        GetMap()->GetSyncIO()._io.HideCursor();
        IO::CoordSet set;
        for ( int vx = GetX() - 1; vx <= GetX() + 1; ++vx ) {
            for ( int vy = GetY() - 1; vy <= GetY() + 1; ++vy ) {
                int vvx = vx, vvy = vy;
                if ( MapToViewCoordinates( vvx, vvy ) )
                    set.insert( IO::Coord( vvx, vvy ) );
            }
        }
        GetMap()->GetSyncIO()._io.ShowCursor( set );
        GetMap()->GetSyncIO().Render();

        uint in = GetMap()->GetAsyncIO().Input();
        if ( in == INPUT_CANCEL || in == INPUT_CONFIRM ) {
            GetMap()->GetSyncIO()._io.HideCursor();
            return false;
        }

        uint dd = GetDirection( in );
        if ( dd != 0 ) {
            d = dd;
            GetMap()->GetSyncIO()._io.HideCursor();
            return true;
        }
    }
}

void Player::FarLook( uint x, uint y ) {
    Tile& t = GetMap()->GetTile( x, y );
    if ( CanSeeTile( x, y ) <= 0.0 ) {
        Bank().PlayerUI( PLAYERUI_LOOK_DARK, this );
        DisplayMessages();
    }
    else {
        ActorInputMap amap;
        ActorList list;
        t.GetActorsOnTile( list );
        for ( uint i = 0; i < list.size(); i++ ) {
            if ( !CanSeeActor( list[ i ] ) ) {
                list.erase( list.begin() + i );
                i--;
            }
        }
        GetActorInputMap( list, amap );

        bool b = false;
        for ( uint i = 0; i < amap.size(); i++ ) {
            ActorList& a = amap[ i ].second;
            std::string name = GetActorInputName( a );
            IO::SymList list;
            if ( !CanSeeActor( a[ 0 ] ) && a[ 0 ] != this )
                list.push_back( IO::Sym( '?', Colour( 128, 128, 128 ) ) );
            else
                a[ 0 ]->Call< BISymbol >( &list );
            ustring s = ustring( "   " + name );
            QueueMessage( s, list );
            b = true;
        }
        if ( !b ) {
            ustring s = ustring( "   " + t.GetName( *GetMap() ) );
            QueueMessage( s, t.GetSymbol() );
        }
        DisplayMessages();
    }
}

bool Player::TryPush( uint direction, int* speedMod )
{
    int x = GetX( direction );
    int y = GetY( direction );
    if ( !GetMap()->IsWithinMap( x, y ) || GetMap()->GetTile( x, y ).IsBlockedByTerrain() ) {
        Bank().PlayerUI( PLAYERUI_PUSH_FAIL, this );
        return true;
    }
    if ( !GetMap()->GetTile( x, y ).IsBlockedByActor() ) {
        Bank().PlayerUI( PLAYERUI_PUSH_NOTHING, this );
        return true;
    }

    ActorList list;
    GetMap()->GetTile( x, y ).GetActorsOnTile< BIBlocking >( list );
    foreach ( Actor* a, list ) {
        bool b = false;
        bool ignore;
        uint uignore;
        a->Call< BIBlocking >( &b, &ignore, &uignore );
        if ( !b )
            continue;
        bool pushed;
        Call< BIOnPush >( a, false, &pushed );
        if ( !pushed )
            return true;
    }

    if ( TryStep( direction, speedMod ) )
        return true;
    // Shouldn't get to here probably?
    return true;
}

bool Player::TryPull( uint direction, int* speedMod )
{
    int x = GetX( 10 - direction );
    int y = GetY( 10 - direction );
    if ( !GetMap()->IsWithinMap( x, y ) || GetMap()->GetTile( x, y ).IsBlockedByTerrain() ) {
        Bank().PlayerUI( PLAYERUI_PUSH_FAIL, this );
        return true;
    }
    if ( !GetMap()->GetTile( x, y ).IsBlockedByActor() ) {
        Bank().PlayerUI( PLAYERUI_PULL_NOTHING, this );
        return true;
    }
    int bx = GetX( direction );
    int by = GetY( direction );
    if ( !GetMap()->IsWithinMap( bx, by ) || GetMap()->GetTile( bx, by ).IsBlocked() ) {
        Bank().PlayerUI( PLAYERUI_PULL_BLOCKED, this );
        return true;
    }

    ActorList list;
    GetMap()->GetTile( x, y ).GetActorsOnTile< BIBlocking >( list );
    foreach ( Actor* a, list ) {
        bool b = false;
        bool ignore;
        uint uignore;
        a->Call< BIBlocking >( &b, &ignore, &uignore );
        if ( !b )
            continue;
        bool pushed;
        Call< BIOnPush >( a, true, &pushed );
        if ( !pushed )
            return true;
    }
    
    if ( TryStep( direction, speedMod ) )
        return true;
    // Shouldn't get to here probably?
    return true;
}

bool Player::TryMove( uint direction, int* speedMod )
{
    if ( TryStep( direction, speedMod ) )
        return true;

    // Blocked by something
    int x = GetX( direction );
    int y = GetY( direction );
    if ( !GetMap()->IsWithinMap( x, y ) || GetMap()->GetTile( x, y ).IsBlockedByTerrain() )
        // Blocked by terrain, allow another action
        return false;

    // Blocked by actor
    if ( !CanSeeTile( x, y ) ) {
        // Blocked in dark
        Bank().PlayerUI( PLAYERUI_BLOCKED_DARK, this );
        return true;
    }
    // Blocked by visible
    ActorList list;
    GetMap()->GetTile( x, y ).GetActorsOnTile< BIBlocking >( list );
    foreach ( Actor* a, list ) {
        bool b = false;
        bool ignore;
        uint uignore;
        a->Call< BIBlocking >( &b, &ignore, &uignore );
        if ( !b )
            continue;
        bool pushed;
        Call< BIOnPush >( a, false, &pushed );
        if ( !pushed )
            return true;
    }
    
    if ( TryStep( direction, speedMod ) )
        return true;
    // Shouldn't get to here probably?
    return true;
}

bool Player::TryStep( uint direction, int* speedMod )
{
    bool moved;
    Call< BIOnMove >( &direction, &moved );
    if ( moved ) {
        // Moved into empty tile
        if ( direction == 1 || direction == 3 || direction == 7 || direction == 9 ) {
            int speedBase;
            Call< BISpeed >( &speedBase );
            *speedMod += int( speedBase * 0.414 + 0.5 );
        }
        return true;
    }
    return false;
}

Behaved* Player::GetBestFireSource()
{
    ItemList unlimited;
    ItemList limited;
    ActorList aunlimited;
    ActorList alimited;
    GetItemsInInventory( unlimited, 0, BIFlammable::FIRE_SOURCE_UNLIMITED );
    GetItemsInInventory( limited,   0, BIFlammable::FIRE_SOURCE_LIMITED );
    GetAdjacentActors(  aunlimited,    BIFlammable::FIRE_SOURCE_UNLIMITED );
    GetAdjacentActors(  alimited,      BIFlammable::FIRE_SOURCE_LIMITED );
    if ( !unlimited.empty() )
        return unlimited[ 0 ];
    if ( !aunlimited.empty() )
        return aunlimited[ 0 ];
    if ( !alimited.empty() )
        return alimited[ 0 ];
    if ( !limited.empty() )
        return limited[ 0 ];
    return 0;
}

uint Player::InputItem( uint doWhat )
{
    uint count = 0;
    ItemInputMap imap;
    bool ignore;
    bool first = true;

    while ( true ) {
        if ( doWhat == PICKUP || doWhat == PICKUP_WIELD ) {
            ActorList actors;
            GetMap()->GetTile( GetX(), GetY() ).GetActorsOnTile( actors );
            GetItemInputMap( actors, imap );
        }
        else {
            ItemList items;
            GetItemsInInventory( items );
            GetItemInputMap( items, imap, true );
        }
        ConstItemList   fireSources;
        ConstActorList afireSources;
        GetItemsInInventory( fireSources, 0, BIFlammable::FIRE_SOURCE );
        GetAdjacentActors(  afireSources,    BIFlammable::FIRE_SOURCE );
        if ( ( doWhat == LIGHT && fireSources.empty() && afireSources.empty() ) || imap.empty() ) {
            GetMap()->GetSyncIO()._io.Clear( 3 );
            return count;
        }
        if ( ( imap.size() > 1 || count || ( !imap.empty() && doWhat != PICKUP && doWhat != PICKUP_WIELD ) ) && first ) {
            if ( doWhat == PICKUP || doWhat == PICKUP_WIELD )
                Bank().PlayerUI( PLAYERUI_QUERY_PICKUP, this );
            else if ( doWhat == DROP )
                Bank().PlayerUI( PLAYERUI_QUERY_DROP, this );
            else if ( doWhat == WEAR )
                Bank().PlayerUI( PLAYERUI_QUERY_WEAR, this );
            else if ( doWhat == WIELD )
                Bank().PlayerUI( PLAYERUI_QUERY_WIELD, this );
            else if ( doWhat == REMOVE )
                Bank().PlayerUI( PLAYERUI_QUERY_REMOVE, this );
            else if ( doWhat == EAT )
                Bank().PlayerUI( PLAYERUI_QUERY_EAT, this );
            else if ( doWhat == LIGHT )
                Bank().PlayerUI( PLAYERUI_QUERY_LIGHT, this );
            first = false;
        }
        DisplayMessages();
        std::string identifiers = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";

        std::vector< std::string > listing;
        std::vector< IO::SymList > symbols;
        int c = -1;
        foreach ( const ItemInputMapEntry& p, imap ) {
            const ItemList& i = p.second;
            int ident = p.first;
            if ( ident >= int( identifiers.length() ) )
                continue;
            uint ic;
            i[ 0 ]->Call< BIItemCategory >( &ic );
            if ( ic != c && doWhat != PICKUP && doWhat != PICKUP_WIELD ) {
                c = ic;
                listing.push_back( Item::CategoryToString( c ) );
                symbols.push_back( IO::SymList() );
            }
            std::string str( 1, identifiers[ ident ] );
            str += "     ";
            str += GetItemInputName( i );
            bool worn = i.size() && i[ 0 ]->IsWorn();
            bool wielded = i.size() && i[ 0 ]->IsWielded();
            if ( worn )
                str += " (worn)";
            if ( wielded )
                str += " (wielded)";
            if ( str.length() > INVENTORY_WIDTH - 2 )
                str = str.substr( 0, INVENTORY_WIDTH - 2 );
            listing.push_back( str );
            IO::SymList list;
            if ( !CanSeeTile( GetX(), GetY() ) && i[ 0 ]->GetActor() != this )
                list.push_back( IO::Sym( '?', Colour( 128, 128, 128 ) ) );
            else
                i[ 0 ]->Call< BISymbol >( &list );
            symbols.push_back( list );
            ident++;
        }

        bool b = false;
        while ( !b ) {
            uint in;

            if ( imap.size() == 1 && !count && ( doWhat == PICKUP || doWhat == PICKUP_WIELD ) )
                in = identifiers[ imap[ 0 ].first ];
            else
                in = RenderList( doWhat != PICKUP && doWhat != PICKUP_WIELD ? "Inventory" : "Ground", listing, symbols, _inventoryPage );

            if ( in == INPUT_CANCEL || in == INPUT_CONFIRM ) {
                GetMap()->GetSyncIO()._io.Clear( 3 );
                DisplayMessages();
                return count;
            }

            // Pick up items all at once
            if ( in == '.' && doWhat == PICKUP ) {
                bool brk = false;
                b = true;
                foreach ( const ItemInputMapEntry& iie, imap ) {
                    int n = 0;
                    foreach ( Item* it, iie.second ) {
                        bool pickedUp;
                        Call< BIOnPickup >( &it, &pickedUp );
                        if ( pickedUp ) {
                            count++;
                            n++;
                        }
                        else {
                            count++;
                            brk = true;
                            break;
                        }
                    }
                    if ( n > 0 ) {
                        std::stringstream ss;
                        ss << n;
                        if ( n > 1 ) {
                            std::string name;
                            iie.second[ 0 ]->Call< BIName >( &name, &ignore, ARTICLE_PLURAL );
                            QueueMessage( "You pick up " + ss.str() + " " + name + ".", brk ? 1 : 0 );
                        }
                        else {
                            std::string name;
                            iie.second[ 0 ]->Call< BIName >( &name, &ignore, ARTICLE_INDEFINITE );
                            QueueMessage( "You pick up " + name + ".", brk ? 1 : 0 );
                        }
                    }
                    if ( brk ) {
                        GetMap()->GetSyncIO()._io.Clear( 3 );
                        return count;
                    }
                    RenderStats();
                }
            }

            if ( !( in >= 'a' && in <= 'z' ) && !( in >= 'A' && in <= 'Z' ) )
                continue;

            uint ident = identifiers.find_first_of( in );
            int i = -1;
            for ( uint j = 0; j < imap.size(); j++ ) {
                if ( imap[ j ].first == ident )
                    i = j;
            }
            if ( i < 0 )
                continue;
            b = true;

            // Pick up items one at a time
            if ( doWhat == PICKUP || doWhat == PICKUP_WIELD ) {
                int n = 0;
                bool brk = false;
                Item* pickedItem = 0;
                foreach ( Item* it, imap[ i ].second ) {
                    bool pickedUp = false;
                    Call< BIOnPickup >( &it, &pickedUp );
                    if ( pickedUp ) {
                        count++;
                        n++;
                        pickedItem = it;
                        if ( doWhat == PICKUP_WIELD )
                            break;
                    }
                    else {
                        count++;
                        brk = true;
                        break;
                    }
                }
                if ( n > 0 ) {
                    std::stringstream ss;
                    ss << n;
                    if ( n > 1 ) {
                        std::string name;
                        imap[ i ].second[ 0 ]->Call< BIName >( &name, &ignore, ARTICLE_PLURAL );
                        QueueMessage( "You pick up " + ss.str() + " " + name + ".", brk ? 1 : 0 );
                    }
                    else {
                        std::string name;
                        imap[ i ].second[ 0 ]->Call< BIName >( &name, &ignore, ARTICLE_INDEFINITE );
                        QueueMessage( "You pick up " + name + ".", brk ? 1 : 0 );
                    }
                    if ( doWhat == PICKUP_WIELD && pickedItem ) {
                        bool wielded;
                        Call< BIOnWield >( &pickedItem, &wielded );
                    }
                }
                if ( brk && imap.size() <= 1 && count == n + 1 ) {
                    GetMap()->GetSyncIO()._io.Clear();
                    return count;
                }
            }
            // Drop items
            else if ( doWhat == DROP ) {
                for ( int j = imap[ i ].second.size() - 1; j >= 0; j-- ) {
                    Item* it = imap[ i ].second[ j ];
                    bool dropped;
                    Call< BIOnDrop >( &it, &dropped );
                    if ( dropped ) {
                        count++;
                        break;
                    }
                }
            }
            // Wear items
            else if ( doWhat == WEAR ) {
                for ( int j = imap[ i ].second.size() - 1; j >= 0; --j ) {
                    Item* it = imap[ i ].second[ j ];
                    uint type;
                    it->Call< BIItemType >( &type );
                    if ( j > 0 && !( type & BIItemType::WEARABLE && !it->IsWorn() && !it->IsWielded() ) )
                        continue;
                    bool worn;
                    Call< BIOnWear >( &it, &worn );
                    if ( worn )
                        count++;
                    break;
                }
            }
            // Wield items
            else if ( doWhat == WIELD ) {
                for ( int j = imap[ i ].second.size() - 1; j >= 0; --j ) {
                    Item* it = imap[ i ].second[ j ];
                    uint type;
                    it->Call< BIItemType >( &type );
                    if ( j > 0 && !( type & BIItemType::WIELDABLE && !it->IsWorn() && !it->IsWielded() ) )
                        continue;
                    bool wielded;
                    Call< BIOnWield >( &it, &wielded );
                    if ( wielded )
                        count++;
                    break;
                }
            }
            // Remove items
            else if ( doWhat == REMOVE ) {
                for ( int j = imap[ i ].second.size() - 1; j >= 0; --j ) {
                    Item* it = imap[ i ].second[ j ];
                    if ( j > 0 && !it->IsWorn() && !it->IsWielded() )
                        continue;
                    bool removed;
                    Call< BIOnRemove >( &it, &removed );
                    if ( removed )
                        count++;
                    break;
                }
            }
            // Eat items
            else if ( doWhat == EAT ) {
                for ( int j = imap[ i ].second.size() - 1; j >= 0; --j ) {
                    Item* it = imap[ i ].second[ j ];
                    if ( j > 0 && !( !it->IsWorn() && !it->IsWielded() ) )
                        continue;
                    bool eaten;
                    Call< BIOnEat >( &it, &eaten );
                    if ( eaten ) {
                        count++;
                        GetMap()->GetSyncIO()._io.Clear( 3 );
                        return count;
                    }
                    break;
                }
            }
            // Set items alight (prefer wielded then worn then other items)
            else if ( doWhat == LIGHT ) {
                if ( Behaved* source = GetBestFireSource() ) {
                    for ( int j = imap[ i ].second.size() - 1; j >= 0; --j ) {
                        Item* it = imap[ i ].second[ j ];
                        if ( j > 0 && !it->IsWielded() && !it->IsWorn() )
                            continue;
                        source->Call< BIOnLight >( this, it );
                        count++;
                        break;
                    }
                }
            }

            RenderStats();
            RenderMap();
        }
    }
}

uint Player::RenderList( const std::string& title, const std::vector< std::string >& listing, const std::vector< IO::SymList >& symbols, int& page )
{
    uint in;
    GetMap()->GetSyncIO()._io.Clear( 3 );

    const int rows = INVENTORY_HEIGHT - 2;
    int rowPages = std::max( 1, int( listing.size() ) - rows + 1 );
    page = std::max( 0, std::min( rowPages - 1, page ) );
    uint height = std::min<uint>( INVENTORY_HEIGHT, listing.size() + 2 );
    uint width = INVENTORY_WIDTH;
    for ( int x = -1; x < int( width ) + 1; ++x ) {
        bool mid = x < int( width / 2 + 2 ) && x >= int( width / 2 - 2 );
        int xx = x + Game::WIDTH - width - 1;
        for ( int y = 0; y < int( height ); ++y ) {
            int yy = y + Game::HEIGHT - STATS_HEIGHT + 1 - height;
            if ( ( x == -1 || x == width ) && y != height - 1 ) {
                GetMap()->GetSyncIO()._io.Draw( Game::CUI_BLACK, xx, yy, 3 );
                continue;
            }
            if ( y == 0 )
                GetMap()->GetSyncIO()._io.Draw( page > 0 && mid ? Sym::UPTRIANGLE : Sym::WALLDHORZ, Game::CUI_MAIN, xx, yy, 3 );
            if ( y == height - 1 )
                GetMap()->GetSyncIO()._io.Draw( page < rowPages - 1 && mid ? Sym::DOWNTRIANGLE : Sym::WALLSHORZ, Game::CUI_MAIN, xx, yy, 3 );
            GetMap()->GetSyncIO()._io.Draw( Game::CUI_BACK, xx, yy, 3 );
        }
    }
    for ( int i = 0; i < rows && i + page < int( listing.size() ); i++ ) {
        GetMap()->GetSyncIO()._io.Draw( listing[ i + page ], !symbols[ i + page ].empty() ? Game::CUI_HEAD : Game::CUI_SUBH, Game::WIDTH - width - 1, Game::HEIGHT - height - STATS_HEIGHT + 2 + i, 3 );
        if ( !symbols[ i + page ].empty() )
            GetMap()->GetSyncIO()._io.Draw( symbols[ i + page ], Game::WIDTH - width + 1, Game::HEIGHT - height - STATS_HEIGHT + 2 + i, 3 );
    }
    uint off = 0;
    foreach ( char c, title ) {
        if ( c != ' ' && page == 0 )
            GetMap()->GetSyncIO()._io.Draw( c, Game::CUI_HEAD, Game::WIDTH - INVENTORY_WIDTH / 2 - 1 - title.length() / 2 + off, Game::HEIGHT - STATS_HEIGHT + 1 - height, 3 );
        ++off;
    }
    GetMap()->GetSyncIO().Render();

    in = GetMap()->GetAsyncIO().Input();
    uint d = GetDirection( in );
    if ( ( d == 1 || d == 2 || d == 3 ) && page < rowPages - 1 )
        page++;
    if ( ( d == 7 || d == 8 || d == 9 ) && page > 0 )
        page--;
    if ( in == INPUT_PGDN )
        page = std::min( page + rows - 1, rowPages - 1 );
    if ( in == INPUT_PGUP )
        page = std::max( page - rows + 1, 0 );
    if ( in == INPUT_HOME )
        page = 0;
    if ( in == INPUT_END )
        page = rowPages - 1;
    return in;
}

void Player::RenderHelp()
{
    std::vector< std::string > listing;
    std::vector< IO::SymList > symbols;
    foreach ( const HelpEntry& h, _help ) {
        if ( h.first == 0 ) {
            listing.push_back( h.second );
            symbols.push_back( IO::SymList() );
            continue;
        }
        listing.push_back( "    " + h.second );
        IO::SymList s;
        s.push_back( IO::Sym( h.first, Game::CUI_MAIN ) );
        symbols.push_back( s );
    }
    
    uint in = 0;
    while ( in != INPUT_CANCEL && in != INPUT_CONFIRM )
        in = RenderList( "Help", listing, symbols, _helpPage );
    GetMap()->GetSyncIO()._io.Clear( 3 );
    DisplayMessages();
}

void Player::RenderStats()
{
    GetMap()->GetSyncIO()._io.Clear( 2 );
    int hpMax, mpMax, sStr, sDex, sInt, ac, magicRes, fireRes;
    Call< BIStats >( &hpMax, &mpMax, &sStr, &sDex, &sInt, &ac, &magicRes, &fireRes );
    std::stringstream ssStr, ssDex, ssInt, ssAC, ssMR, ssFR;
    ssStr << sStr; ssDex << sDex; ssInt << sInt; 
    ssAC << ac; ssMR << magicRes; ssFR << fireRes;

    const std::string line1 = "Str     Dex     Int     AC     MR     FR    ";
    const std::string line2 = "HP            MP            WEIGHT           ";
    for ( uint x = 0; x < Game::WIDTH; ++x ) {
        for ( uint y = 0; y < STATS_HEIGHT; ++y ) {
            if ( x == 0 || x == Game::WIDTH - 1 ) {
                GetMap()->GetSyncIO()._io.Draw( Game::CUI_BLACK, x, y + Game::HEIGHT - STATS_HEIGHT, 2 );
                continue;
            }
            GetMap()->GetSyncIO()._io.Draw( Game::CUI_BACK, x, y + Game::HEIGHT - STATS_HEIGHT, 2 );
            if ( y == 0 || y == STATS_HEIGHT - 1 )
                GetMap()->GetSyncIO()._io.Draw( Sym::WALLDHORZ, Game::CUI_MAIN, x, y + Game::HEIGHT - STATS_HEIGHT, 2 );
        }
    }

    GetMap()->GetSyncIO()._io.Draw( line1, Game::CUI_SUBH,  1, Game::HEIGHT - STATS_HEIGHT + 1, 2 );
    GetMap()->GetSyncIO()._io.Draw( ":",   Game::CUI_SUBH,  4, Game::HEIGHT - STATS_HEIGHT + 1, 2 );
    GetMap()->GetSyncIO()._io.Draw( ":",   Game::CUI_SUBH, 12, Game::HEIGHT - STATS_HEIGHT + 1, 2 );
    GetMap()->GetSyncIO()._io.Draw( ":",   Game::CUI_SUBH, 20, Game::HEIGHT - STATS_HEIGHT + 1, 2 );
    GetMap()->GetSyncIO()._io.Draw( ":",   Game::CUI_SUBH, 27, Game::HEIGHT - STATS_HEIGHT + 1, 2 );
    GetMap()->GetSyncIO()._io.Draw( ":",   Game::CUI_SUBH, 34, Game::HEIGHT - STATS_HEIGHT + 1, 2 );
    GetMap()->GetSyncIO()._io.Draw( ":",   Game::CUI_SUBH, 41, Game::HEIGHT - STATS_HEIGHT + 1, 2 );

    GetMap()->GetSyncIO()._io.Draw( ssStr.str(), Game::CUI_HEAD,  6, Game::HEIGHT - STATS_HEIGHT + 1, 2 ); 
    GetMap()->GetSyncIO()._io.Draw( ssDex.str(), Game::CUI_HEAD, 14, Game::HEIGHT - STATS_HEIGHT + 1, 2 ); 
    GetMap()->GetSyncIO()._io.Draw( ssInt.str(), Game::CUI_HEAD, 22, Game::HEIGHT - STATS_HEIGHT + 1, 2 );
    GetMap()->GetSyncIO()._io.Draw( ssAC.str(),  Game::CUI_HEAD, 29, Game::HEIGHT - STATS_HEIGHT + 1, 2 );
    GetMap()->GetSyncIO()._io.Draw( ssMR.str(),  Game::CUI_HEAD, 36, Game::HEIGHT - STATS_HEIGHT + 1, 2 );
    GetMap()->GetSyncIO()._io.Draw( ssFR.str(),  Game::CUI_HEAD, 43, Game::HEIGHT - STATS_HEIGHT + 1, 2 );

    int weightMax;
    double hpRegen, mpRegen;
    Call< BIDerivedStats >( &hpRegen, &mpRegen, &weightMax );
    std::stringstream ssHp, ssHpMax, ssMp, ssMpMax, ssWeight, ssWeightMax;
    ssHp << _hp; ssMp << _mp; ssHpMax << hpMax; ssMpMax << mpMax; ssWeight << CountInventoryWeight() / 20; ssWeightMax << weightMax / 20;

    GetMap()->GetSyncIO()._io.Draw( line2, Game::CUI_SUBH,  1, Game::HEIGHT - STATS_HEIGHT + 2, 2 );
    GetMap()->GetSyncIO()._io.Draw( ":",   Game::CUI_SUBH,  3, Game::HEIGHT - STATS_HEIGHT + 2, 2 );
    GetMap()->GetSyncIO()._io.Draw( ":",   Game::CUI_SUBH, 17, Game::HEIGHT - STATS_HEIGHT + 2, 2 );
    GetMap()->GetSyncIO()._io.Draw( ":",   Game::CUI_SUBH, 35, Game::HEIGHT - STATS_HEIGHT + 2, 2 );
    double d = double( _hp ) / double( hpMax );
    GetMap()->GetSyncIO()._io.Draw( ssHp.str(),     d <= 0.2 ? Game::CUI_BAD : d <= 0.4 ? Game::CUI_WARNING : Game::CUI_HEAD, 5, Game::HEIGHT - STATS_HEIGHT + 2, 2 );
    GetMap()->GetSyncIO()._io.Draw( "/", Game::CUI_SUBH, 6 + ssHp.str().length(), Game::HEIGHT - STATS_HEIGHT + 2, 2 );
    GetMap()->GetSyncIO()._io.Draw( ssHpMax.str(),  Game::CUI_HEAD, 8 + ssHp.str().length(), Game::HEIGHT - STATS_HEIGHT + 2, 2 );
    GetMap()->GetSyncIO()._io.Draw( ssMp.str(),     Game::CUI_HEAD, 19, Game::HEIGHT - STATS_HEIGHT + 2, 2 );
    GetMap()->GetSyncIO()._io.Draw( "/", Game::CUI_SUBH, 20 + ssMp.str().length(), Game::HEIGHT - STATS_HEIGHT + 2, 2 );
    GetMap()->GetSyncIO()._io.Draw( ssMpMax.str(),  Game::CUI_HEAD, 22 + ssMp.str().length(), Game::HEIGHT - STATS_HEIGHT + 2, 2 );
    GetMap()->GetSyncIO()._io.Draw( ssWeight.str(), Game::CUI_HEAD, 37, Game::HEIGHT - STATS_HEIGHT + 2, 2 );
    GetMap()->GetSyncIO()._io.Draw( "/", Game::CUI_SUBH, 38 + ssWeight.str().length(), Game::HEIGHT - STATS_HEIGHT + 2, 2 );
    GetMap()->GetSyncIO()._io.Draw( ssWeightMax.str(),  Game::CUI_HEAD, 40 + ssWeight.str().length(), Game::HEIGHT - STATS_HEIGHT + 2, 2 );

    // Render status effects
    StatusEffectList list;
    Call< BIStatusEffects >( &list );
    std::stable_sort( list.rbegin(), list.rend() );
    uint len1 = Game::WIDTH - 2 - line1.length();
    uint len2 = Game::WIDTH - 2 - line2.length();
    while ( !list.empty() && list.rbegin()->first.length() + 2 <= len1 ) {
        Colour c = Game::CUI_HEAD;
        if ( list.rbegin()->second == STATUS_GOOD )
            c = Game::CUI_GOOD;
        else if ( list.rbegin()->second == STATUS_BAD )
            c = Game::CUI_BAD;
        GetMap()->GetSyncIO()._io.Draw( list.rbegin()->first, c, Game::WIDTH - len1, Game::HEIGHT - STATS_HEIGHT + 1, 2 );
        if ( list.size() > 1 )
            GetMap()->GetSyncIO()._io.Draw( ",", Game::CUI_SUBH, Game::WIDTH - len1 + list.rbegin()->first.length(), Game::HEIGHT - STATS_HEIGHT + 1, 2 );
        len1 -= list.rbegin()->first.length() + 2;
        list.pop_back();
    }
    while ( !list.empty() && list.rbegin()->first.length() + 2 <= len2 ) {
        Colour c = Game::CUI_HEAD;
        if ( list.rbegin()->second == STATUS_GOOD )
            c = Game::CUI_GOOD;
        else if ( list.rbegin()->second == STATUS_BAD )
            c = Game::CUI_BAD;
        GetMap()->GetSyncIO()._io.Draw( list.rbegin()->first, c, Game::WIDTH - len2, Game::HEIGHT - STATS_HEIGHT + 2, 2 );
        if ( list.size() > 1 )
            GetMap()->GetSyncIO()._io.Draw( ",", Game::CUI_SUBH, Game::WIDTH - len2 + list.rbegin()->first.length(), Game::HEIGHT - STATS_HEIGHT + 2, 2 );
        len2 -= list.rbegin()->first.length() + 2;
        list.pop_back();
    }
    if ( !list.empty() )
        GetMap()->GetSyncIO()._io.Draw( "...", Game::CUI_SUBH, Game::WIDTH - 4, Game::HEIGHT - STATS_HEIGHT + 2, 2 );

    // Draw equipment
    ItemList wielded, worn;
    GetWieldedItems( 0, wielded );
    GetWornItems( 0, worn );
    int ex = 5;
    foreach ( const Item* it, wielded ) {
        IO::SymList list;
        it->Call< BISymbol >( &list );
        GetMap()->GetSyncIO()._io.Draw( list, ex, Game::HEIGHT - STATS_HEIGHT, 2 );
        ++ex;
    }
    ++ex;
    foreach ( const Item* it, worn ) {
        IO::SymList list;
        it->Call< BISymbol >( &list );
        GetMap()->GetSyncIO()._io.Draw( list, ex, Game::HEIGHT - STATS_HEIGHT, 2 );
        ++ex;
    }
    ++ex;

    // Draw tabs
    bool inventoryFire = false;
    ItemList inventory;
    GetItemsInInventory( inventory );
    foreach ( Item* i, inventory ) {
        uint fire = 0;
        i->Call< BIFire >( &fire );
        if ( fire & BIFire::FIRE_ON_FIRE && !i->IsWielded() && !i->IsWorn() ) {
            inventoryFire = true;
            break;
        }
    }
    std::string s = "i      ?";
    GetMap()->GetSyncIO()._io.Draw( s, Game::CUI_HEAD, Game::WIDTH - INVENTORY_WIDTH / 2 - 1 - s.length() / 2, Game::HEIGHT - STATS_HEIGHT, 2 );
    if ( inventoryFire ) {
        IO::SymList list;
        list.push_back( IO::Sym( 'i', Game::CUI_HEAD ) );
        list.push_back( IO::Sym( Sym::DOUBLEEXCLAMATION, Colour( 255, 0, 0 ) ) );
        GetMap()->GetSyncIO()._io.Draw( list, Game::WIDTH - INVENTORY_WIDTH / 2 - 1 - s.length() / 2, Game::HEIGHT - STATS_HEIGHT, 2 );
    }
    ustring us( 4, uchar( Sym::UPTRIANGLE ) );
    us = ustring( uchar( Sym::WALLDHORZ ) ) + us + ustring( uchar( Sym::WALLDHORZ ) );
    GetMap()->GetSyncIO()._io.Draw( us, Game::CUI_MAIN, Game::WIDTH - INVENTORY_WIDTH / 2 - 1 - us.length() / 2, Game::HEIGHT - STATS_HEIGHT, 2 );
}
